﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Autofac;
using log4net;

namespace PortableVersion.Core.FileSystem.Services
{
    public class CheckInService : ISynchronizationOrchestrator
    {
        private readonly IVersioningSource<FileItem> _source;
        private readonly IFolderBrowserService _sourceFolderService;
        private readonly IMetadataService<FileItem> _metaService;
        private readonly IConfigurationProvider _config;
        private readonly ILog _log;
        private readonly IDiffService<FileItem> _diffService;
        private readonly IContainer _container;
        private readonly IDiffApplicationService<FileItem> _diffApplyer;
        private readonly IConflictResolutionService<FileItem> _conflictResolver;
        public event EventHandler<DiffEventArgs> DiffsRecalculated;
        public event EventHandler<DiffApplicationEventArgs> CheckInFinished;

        public IVersioningSource<FileItem> Source
        {
            get { return _source; }
        }

        public IVersioningSource<FileItem> Target { get; private set; }

        public void Run()
        {
            _log.Info("Started running checkin service.");
            //This is a hack. To avoid running check in again on an existing check in.
            //Ideally we should do get-latest for every file that is sucessfully checked in.
            //TODO: Fix this hack when the get-latest functionality is implemented.
            if (File.Exists(Path.Combine(_source.Path, "checked.in")))
                throw new InvalidOperationException(String.Format("Slave foler '{0}' is already checked in. Delete the folder and get a fresh check-out.", _source.Path));
            //Read the metadata
            _metaService.Load(Path.Combine(_source.Path, _config.GetValue<string>(ConfigurationProvider.Keys.MetadataFileName)));
            //Read the target directory
            Target =
                _container.Resolve<IVersioningSource<FileItem>>(new[]
                                                                    {
                                                                        new NamedParameter("path", _metaService.MasterPath),
                                                                        new NamedParameter("position", FilePosition.Master)
                                                                    });
            var targetBrowser =
                _container.Resolve<IFolderBrowserService>(new[]
                                                              {
                                                                  new TypedParameter(typeof(IVersioningSource<FileItem>), Target)
                                                              });
            _log.InfoFormat(@"Reading the target (master) directory {0}", Target.Path);
            targetBrowser.Populate(Target);
            _log.InfoFormat(@"Reading the source (slave) directory {0}", _source.Path);
            //Read the source directory
            _sourceFolderService.Populate(_source);
            _log.Info(@"Generating diff");
            //Generate the diffs
            var diffs = _diffService.GenerateDiff(_source, Target, _metaService).Where(d => d.DiffType != DiffType.Ignore).ToList();
            var confPath = Path.Combine(_source.Path,_config.GetValue<string>(
                                            ConfigurationProvider.Keys.ConflictFileName));
            _log.InfoFormat(@"Loading the conflict state from {0}", confPath);
            //Queue diff actions
            _conflictResolver.LoadState(confPath);
            var safeDiffs = diffs.Where(d => d.DiffType == DiffType.Safe);
            var conflicts = diffs.Except(safeDiffs);
            foreach (var conflict in conflicts)
            {
                _conflictResolver.QueueConflict(conflict);
            }
            foreach (var safeDiff in safeDiffs)
            {
                _diffApplyer.QueueDiffApplication(safeDiff);
            }
            if (DiffsRecalculated != null)
            {
                DiffsRecalculated(this, new DiffEventArgs(safeDiffs.ToList(), conflicts.ToList(), _conflictResolver.ConflictState));
            }
            _log.Info(@"Loocking for unresolved conflicts.");

            //Check conflict state
            if (_conflictResolver.GetUnhandledConflicts().Any())
            {
                _log.Info(@"Some unresolved conflict remains. Check-in can not continue until all conflicts are resolved.");
                throw new DataMisalignedException(
                    @"Some unresolved conflict remains. Check-in can not continue until all conflicts are resolved.");
                //We can't do much if there is an unhandeled conflict
            }
            _log.Info(@"Applying conflict resolutions.");
            _conflictResolver.SetRootPaths(_source.Path, Target.Path);
            var confResult = _conflictResolver.ResolveAllConflicts();
            _log.Info(@"Applying safe changes.");
            var diffResult = _diffApplyer.ApplyAllDiffs();
            var apps = confResult.Concat(diffResult).ToList();
            if ( apps.Any() && (CheckInFinished != null) )
            {
                CheckInFinished(this, new DiffApplicationEventArgs(apps));
            }
            //This is a hack. To avoid running check in again on an existing check in.
            //Ideally we should do get-latest for every file that is sucessfully checked in.
            //TODO: Fix this hack when the get-latest functionality is implemented.
            File.WriteAllText(Path.Combine(_source.Path, "checked.in"), @"This file is already checked in.");
        }

        public CheckInService(IVersioningSource<FileItem> source, IFolderBrowserService sourceSourceFolderService, IMetadataService<FileItem> metaService,
            IConfigurationProvider config, ILog log, IDiffService<FileItem> diffService, IContainer container, 
            IDiffApplicationService<FileItem> diffApplyer, IConflictResolutionService<FileItem> conflictResolver
            )
        {
            _source = source;
            _container = container;
            _diffApplyer = diffApplyer;
            _conflictResolver = conflictResolver;
            _sourceFolderService = sourceSourceFolderService;
            _metaService = metaService;
            _config = config;
            _log = log;
            _diffService = diffService;
        }
    }

    public class DiffApplicationEventArgs : EventArgs
    {
        public List<DiffApplicationResult> Diffs { get; private set; }
        public DiffApplicationEventArgs(List<DiffApplicationResult> diffs)
        {
            Diffs = diffs;
        }
    }

    public class DiffEventArgs : EventArgs
    {
        public IEnumerable<Diff<FileItem>> SafeDiffs { get; private set; }
        public IEnumerable<Diff<FileItem>> Conflicts { get; private set; }
        public ConflictState ConflictState { get; private set; }
        public DiffEventArgs(IEnumerable<Diff<FileItem>> safeDiffs, IEnumerable<Diff<FileItem>> conflicts, ConflictState conflictState)
        {
            SafeDiffs = safeDiffs;
            Conflicts = conflicts;
            ConflictState = conflictState;
        }
    }
}
